
package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointInside;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_LABEL;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_TEMPLATE;
import static xj.graph2d.DrawShapeConstants.ROLE_NODE_TITLE_LABEL;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.Set;

import javax.swing.SwingConstants;

import xj.graph2d.BoxGroup;
import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.Label;
import xj.lang.CodeWriter;
import xj.util.gui.Rectangle2DFloat;

public class RectangleGridShape 
  extends CompartmentalRectangleShape 
  implements VerticalCompartments {

  public RectangleGridShape() {
    this(null, null, false, false);
  }

  public RectangleGridShape(boolean createComp) {
    this(null, null, false, createComp);
  }

  public RectangleGridShape(float[] hdividers, float[] vdividers) {
    this(hdividers, vdividers, false, false);
  }

  public RectangleGridShape(float[] hdividers, float[] vdividers, boolean filled) {
    this(hdividers, vdividers, filled, false);
  }

  public RectangleGridShape(float[] hdividers, float[] vdividers,
      boolean filled, boolean createComp) {
    super(hdividers, filled, createComp);
    vadapter = new VerticalCompartmentsAdapter(this);
    vadapter.setVerticalDividers(vdividers);
  }

  public RectangleGridShape(float x1, float y1, float x2, float y2,
      float[] hdividers, float[] vdividers) {
    this(x1, y1, x2, y2, hdividers, vdividers, false, false);
  }

  public RectangleGridShape(float x1, float y1, float x2, float y2,
      float[] hdividers, float[] vdividers, boolean filled) {
    this(x1, y1, x2, y2, hdividers, vdividers, filled, false);
  }

  public RectangleGridShape(float x1, float y1, float x2, float y2,
      float[] hdividers, float[] vdividers, boolean filled, boolean createComp) {
    super(x1, y1, x2, y2, hdividers, filled, createComp);
    vadapter = new VerticalCompartmentsAdapter(this);
    vadapter.setVerticalDividers(vdividers);
  }

  @Override
  public DrawShape makeInstance() {
    return new RectangleGridShape(null, null, filled, comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof RectangleGridShape) {
      RectangleGridShape crect = (RectangleGridShape) s;
      if (vadapter != null) {
	vadapter.copyDividers(crect.vadapter);
      }
    }
  }

  @Override
  public void draw(Graphics2D g2) {
    super.draw(g2);
    if (vadapter != null) {
      vadapter.drawVerticalDividers(g2);
    }
  }

  @Override
  public void drawSubShapes(Graphics2D g2, int mode, 
			    DrawShape selectedShape,
			    Set<DrawShape> selectionSet,
			    boolean reverseOrder) {
    float[] dividers = getVerticalDividers();
    if (dividers == null) {
      super.drawSubShapes(g2, mode, selectedShape, selectionSet);
    } else {
      if (subshapes != null) {
	int n = subshapes.size();
	int m = dividers.length;

	if (m >= 1) {
	  float x = (float) Math.min(p1.getX(), p2.getX());
	  float y = (float) Math.min(p1.getY(), p2.getY());
	  float h = (float) Math.abs(p1.getY() - p2.getY());
	  float w = (float) Math.abs(p1.getX() - p2.getX());
	  float xdiv = 0;
	  float vdiv = 0;
	  for (int i = 0; i <= m; i++) {
	    if (i < m) {
	      vdiv = dividers[i] * h;
	    } else {
	      vdiv = h - xdiv;
	    }
	    DrawShape s = getShapeInCompartment(i);
	    if (s != null && s.isVisible()) {
	      g2.setClip(new Rectangle2DFloat(x, y + xdiv, w, vdiv));
	      s.drawShape(g2, mode, selectedShape, selectionSet);
	    }
	    xdiv += vdiv;
	  }
	}

	if (clipSubshapes) {
	  g2.setClip(getEndRect());
	} else {
	  g2.setClip(null);
	}

	DrawShape tempShape = null;
	for (int i = 0; i < n; i++) {
	  DrawShape s = subshapes.get(i);
	  if (s.isVisible()) {
	    int role = s.getRole();
	    if (role < COMPARTMENT_ROLE_BASE
		|| role > (COMPARTMENT_ROLE_BASE + m)) {
	      if (role == ROLE_NODE_TEMPLATE) {
		tempShape = s;
	      } else {
		s.drawShape(g2, mode, selectedShape, selectionSet);
	      }
	    }
	  }
	}
	if (clipSubshapes) {
	  g2.setClip(null);
	}

	if (tempShape != null) {
	  tempShape.drawShape(g2, mode, selectedShape, selectionSet);
	}
      }
    }
  }

  @Override
  public void drawOutline(Graphics2D g2) {
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      g2.setColor(attr.getPenColor());
    } else {
      g2.setColor(DrawAttr.getDefaultPenColor());
    }
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    g2.draw(new Rectangle2D.Float(x, y, w, h));
    if (showVerticalGutter && w > gutterWidth * 2) {
      g2.draw(new Line2D.Float(x + gutterWidth, y, x + gutterWidth, y + h));
      g2.draw(new Line2D.Float(x + w - gutterWidth, y, x + w - gutterWidth, y
	  + h));
    }
    if (vadapter != null) {
      vadapter.drawVerticalDividers(g2);
    }
    drawSubShapesOutline(g2);
  }

  @Override
  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    super.drawOutline(g2, x1, y1, x2, y2);
    if (vadapter != null) {
      float x = Math.min(x1, x2);
      float y = Math.min(y1, y2);
      float h = Math.abs(y1 - y2);
      float w = Math.abs(x1 - x2);
      vadapter.drawVerticalDividers(g2, x, y, w, h);
    }
  }

  @Override
  public void drawFrameEx(Graphics2D g2, float marksize,
			  boolean fill, Color frameColor) {
    if (vadapter != null) {
      vadapter.drawFrame(g2, marksize, fill, frameColor);
    }
  }

  public float[] getVerticalDividers() {
    if (vadapter != null) {
      return vadapter.getVerticalDividers();
    }
    return null;
  }

  public void setVerticalDividers(float[] dividers) {
    if (vadapter != null) {
      vadapter.setVerticalDividers(dividers);
    }
  }

  public int getNumberOfVerticalCompartments() {
    if (vadapter != null) {
      return vadapter.getNumberOfVerticalCompartments();
    }
    return 1;
  }

  public boolean isNumberOfVerticalCompartmentsFixed() {
    if (vadapter != null) {
      return vadapter.isNumberOfVerticalCompartmentsFixed();
    }
    return false;
  }

  public int inVerticalCompartment(DrawShape s) {
    if (vadapter != null) {
      return vadapter.inVerticalCompartment(s);
    }
    return -1;
  }

  public void moveVerticalDivider(int i, float dy) {
    if (vadapter != null) {
      vadapter.moveVerticalDivider(i, dy);
      doLayout();
    }
  }

  public void addVerticalDivider(int p, float dy) {
    if (vadapter != null) {
      vadapter.addVerticalDivider(p, dy);
      doLayout();
    }
  }

  public void removeVerticalDivider(int p) {
    if (vadapter != null) {
      vadapter.removeVerticalDivider(p);
      doLayout();
    }
  }

  @Override
  public void setShapeInCompartment(DrawShape shape, int i) {
    int role = COMPARTMENT_ROLE_BASE + i;
    DrawShape s0 = findInRole(role);
    if (s0 != null) {
      removeShape(s0);
    }
    if (shape != null) {
      shape.setRole(role);
      addShape(shape);
      positionCompartmentContent(i);
    }
  }

  @Override
  public DrawShape removeShapeInCompartment(int i) {
    int role = COMPARTMENT_ROLE_BASE + i;
    DrawShape s0 = findInRole(role);
    if (s0 != null) {
      removeShape(s0);
    }
    return s0;
  }

  @Override
  public DrawShape getShapeInCompartment(int i) {
    int role = COMPARTMENT_ROLE_BASE + i;
    DrawShape shape = findInRole(role);
    return shape;
  }

  @Override
  public int isInside(float px, float py) {
    int result = super.isInside(px, py);
    if (result == PointInside) {
      if (vadapter != null) {
	int d = vadapter.onVerticalDivider(px, py);
	if (d != -1) {
	  result = d;
	}
      }
    }
    return result;
  }

  @Override
  public void setLabelOffset() {
    Label label = findLabel();
    if (label != null) {
      if (label.getRole() == ROLE_NODE_TITLE_LABEL) {
	float[] div = vadapter.getVerticalDividers();
	if (div.length > 0) {
	  float x = (float) bounds.getX();
	  float y = (float) bounds.getY();
	  float w = (float) bounds.getWidth();
	  float h = (float) bounds.getHeight();
	  float h0 = div[0] * h;
	  setLabelOffset(x, y, w, h0);
	}
      } else if (label.getRole() == ROLE_NODE_LABEL) {
	setLabelOffset(bounds);
      } else {
	setLabelOffset(bounds);
      }
    }
  }

  @Override
  public void positionLabel() {
    Label label = findLabel();
    if (label != null) {
      if (label.getRole() == ROLE_NODE_TITLE_LABEL) {
	float x = (float) Math.min(p1.getX(), p2.getX());
	float y = (float) Math.min(p1.getY(), p2.getY());
	float w = (float) Math.abs(p1.getX() - p2.getX());
	float h = (float) Math.abs(p1.getY() - p2.getY());
	float[] div = vadapter.getVerticalDividers();

	float dh = 0;
	DrawShape tshape = findTemplateShape();
	if (tshape != null) {
	  Rectangle2D lb = label.getBounds();
	  Rectangle2D tb = tshape.getBounds();
	  float y1 = (float) tb.getY();
	  float x1 = (float) tb.getX();
	  float h1 = (float) tb.getHeight();
	  float lx = (float) lb.getX();
	  float lw = (float) lb.getWidth();
	  if ((lx + lw) > x1) {
	    dh = (y1 + h1) - y;
	  }
	}

	if (div != null && div.length > 0) {
	  float h0 = div[0] * h;
	  if (dh < 0 || dh > h0) {
	    dh = 0;
	  }
	  positionLabel(x, y + dh, w, h0 - dh);
	} else {
	  if (dh < 0 || dh > h) {
	    dh = 0;
	  }
	  positionLabel(x, y + dh, w, h - dh);
	}
      } else if (label.getRole() == ROLE_NODE_LABEL) {
	positionLabel(bounds);
      } else {
	positionLabel(bounds);
      }
    }
  }

  @Override
  protected void positionCompartmentContent(int i) {
    // System.out.println("CompartmentalRectangleShape.positionCompartmentContent()
    // i=" + i);

    float[] div = vadapter.getVerticalDividers();
    if (div != null) {
      int n = div.length;
      if (n > 0 && i >= 0 && i <= n) {
	DrawShape shape = getShapeInCompartment(i);
	if (shape != null) {

	  // System.out.println("CompartmentalRectangleShape.positionCompartmentContent()
	  // [" + i +
	  // "] shape: " + shape);

	  float x = (float) Math.min(p1.getX(), p2.getX());
	  float y = (float) Math.min(p1.getY(), p2.getY());
	  float w = (float) Math.abs(p1.getX() - p2.getX());
	  float h = (float) Math.abs(p1.getY() - p2.getY());

	  float h0 = 0;
	  float h1 = h;
	  for (int j = 0; j < i; j++) {
	    h0 += div[j] * h;
	  }
	  if (i < n) {
	    h1 = h0 + div[i] * h;
	  }
	  int gw = 0;
	  if (isShowVerticalGutter()) {
	    gw = getGutterWidth();
	    x += gw;
	    w -= 2 * gw;
	  }

	  float dx = 0, dy = 0;
	  if (w > cpadx * 2) {
	    dx = cpadx;
	    x += dx;
	    w -= dx * 2;
	  }
	  float cy = y + h0;
	  float ch = h1 - h0;
	  if (ch > cpady * 2) {
	    dy = cpady;
	    cy += dy;
	    ch -= dy * 2;
	  }
	  if (shape instanceof BoxGroup) {
	    BoxGroup box = (BoxGroup) shape;
	    // box.setBounds(x, y + h0, w, (h1 - h0));
	    box.doLayout();
	    positionShape(shape, x, cy, w, ch, SwingConstants.TOP,
		SwingConstants.LEFT);
	  } else {
	    positionShape(shape, x, cy, w, ch);
	  }
	}
      }
    }
  }

  @Override
  public void doLayout() {
    // System.out.println("CompartmentalRectangleShape.doLayout()");

    positionTemplateShape();
    positionLabel();

    int n = vadapter.getNumberOfVerticalCompartments();
    for (int i = 0; i <= n; i++) {
      positionCompartmentContent(i);
    }

  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
      String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      if (vadapter != null) {
	vadapter.writeXML(out, namespacePrefix);
      }
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      String nodename = e.getLocalName();
      if ("VDividers".equals(nodename)) {
	if (vadapter != null) {
	  vadapter.setAttrFromXML(e, namespaceURI);
	}
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  protected VerticalCompartmentsAdapter vadapter;

  protected static final int COMPARTMENT_ROLE_BASE = 0;

  protected static final int cpadx = 5;

  protected static final int cpady = 5;

}
